#!/usr/bin/env ruby -wKU

#
# Dxdiag wine version
# English language support
#
# Copyright (C) 2011 Drew Wilder-Goodwin
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
#

# This script compares two files output by DxDiag
# TODO: handle XML format
# TODO: make plain text file parsing smarter
# TODO: make verbose a command line option

$verbose = true

# Generic error handling
# Call with msg to display msg and exit
# Call without msg to display usage information and exit
def error(msg = nil)
  if msg then
    puts "Error: #{msg}"
  else
    puts "Compare two files output by DxDiag"
    puts "Usage: test_output.rb file1 file2"
  end
  exit(1)
end

# Print out a test heading to indicate a new test being run
# Test heading is in the following format:
# -----------------------------------
# Description of test about to be run
# -----------------------------------
def test_heading(msg = "message not supplied")
  if $verbose
    msg.length.times { print '-' }
    puts "\n#{msg}"
    msg.length.times { print '-' }
    puts
  else
    print msg
  end
end

# Output test summary to indicate results of completed test
# Success should be true if test passed, false otherwise
def test_summary(success = false, msg = "message not supplied")
  print "Test Summary: " if $verbose
  if success
    puts "PASSED"
  else
    puts "FAILED - #{msg}"
  end
end

class PlainTextFile
  attr_reader :sections
  
  def initialize(file)

    contents = File.read(file)
    areas = contents.split(/-+\r*\n/).drop(1)
    areas.collect! { |x| x.strip }
    
    @sections = Hash.new
    
    # FIXME: this does not work if the last section is empty and
    # there is no newline at the end of the file
    if areas.size.odd?
      error("Detected improperly formatted file")
    end
    
    Hash[*areas.flatten].each do |heading, rawtext|
      
      sections[heading] = Hash.new
      sections[heading]["raw"] = rawtext
      
      rawtext.each_line do |line|
        next unless line.include?(":")
        property,value = line.split(":", 2)
        sections[heading][property.strip] = value.strip
      end
      
    end

  end
  
  def section_count
    @sections.size
  end
  
  def has_section?(heading)
    @sections[heading].nil? == false
  end
  
  def has_section_property?(heading, property)
    has_section?(heading) and @sections[heading][property].nil? == false
  end
  
  def section_property_count(heading)
    return false if not has_section?(heading)
    section_properties(heading).size
  end
  
  def section_properties(heading)
    @sections[heading].delete_if{|k,v| k == "raw"}
  end
  
  def section_property(heading, property)
    return false if not has_section_property?(heading, property)
    return @sections[heading][property]
  end

end

def section_name_comparison(file1,file2)
  missing_count = 0
  file1.sections.each_key do |heading|
    print "Found #{heading} in file1, checking file2... " if $verbose
    if file2.has_section?(heading)
      puts "found" if $verbose
    else
      missing_count += 1
      puts "not found" if $verbose
    end
  end
  missing_count
end

def section_property_num_comparison(file1, file2)
  non_match_count = 0
  file1.sections.each_key do |heading|
    
    print "Comparing #{heading} property count... " if $verbose
    if not file2.has_section?(heading)
        puts "not matched, section does not exist in file2" if $verbose
        non_match_count += 1
        next
    end
    
    size1 = file1.section_property_count(heading)
    size2 = file2.section_property_count(heading)
    if size1 == size2
      puts "matched" if $verbose
    else
      non_match_count += 1
      puts "not matched, file1 has #{size1} properties for this heading, file2 #{size2}" if $verbose
    end

  end
  non_match_count
end

def section_property_name_comparison(file1, file2)
  missing_count = 0
  file1.sections.each_key do |heading|
    
    print "Comparing #{heading} property names... " if $verbose
    if not file2.has_section?(heading)
      missing_count += file1.section_property_count(heading)
      puts "***** Cannot compare properties in #{heading}, section missing from file2" if $verbose
      next
    end
    
    file1.section_properties(heading).each_key do |property|
      
      print "Checking for property #{heading}.#{property}... " if $verbose
      
      if not file2.has_section_property?(heading, property)
        missing_count += 1
        puts "not found" if $verbose
      else
        puts "found" if $verbose
      end
      
    end
  end
  missing_count
end

def hierarchical_property_comparison(file1, file2)
  non_match_count = 0
  file1.sections.each_key do |heading|
    
    next if not file2.has_section?(heading)
    
    file1.section_properties(heading).each do |property, value|
      
      print "Comparing #{heading}.#{property} with value #{value}... " if $verbose
      
      if not file2.has_section_property?(heading, property)
        non_match_count += 1
        puts "cannot compare, missing in file2" if $verbose
        next
      end
      
      if file2.section_property(heading, property) == value then
        puts "matched" if $verbose
      else
        non_match_count += 1
        puts "not matched, file2 is #{file2.section_property(heading, property)}" if $verbose
      end
    end
  end
  non_match_count
end



#########################################################################
########################    BEGIN MAIN PROGRAM   ######################## 
#########################################################################


# Two paramaters must be specified to run tests
if ARGV.size != 2 then
  error
end

# The two arguments are the two files two compare
file1_path = ARGV[0]
file2_path = ARGV[1]

# Both files must exist and be readable to perform comparison
if not File.readable? file1_path then
  error "input file #{file1_path} does not exist or is not readable"
end
if (not File.readable? file2_path) then
  error "input file #{file2_path} does not exist or is not readable"
end

puts "Starting comparison tests for #{file1_path} and #{file2_path}"

file1 = PlainTextFile.new(file1_path)
file2 = PlainTextFile.new(file2_path)

# Test: check for equal number of sections
test_heading("Test for equal number of sections...")
test_summary(file1.section_count == file2.section_count,
    "File1 has #{file1.section_count} sections, File2 has #{file2.section_count}")

# Test: check for matching sections
test_heading("Test that all sections in file1 exist in file2..")
section_missing_count = section_name_comparison(file1,file2)
test_summary(section_missing_count == 0,
    "#{section_missing_count} sections in file1 not present in file2")
        
# Test: check for equal number properties in each section
test_heading("Test for equal number of properties per section...")
section_num_properties_mismatch_count = section_property_num_comparison(file1,file2)
test_summary(section_num_properties_mismatch_count == 0,
    "#{section_num_properties_mismatch_count} section with differing number of properties")

# Test: check for same properties
test_heading("Test that all properties in file1 exist in file2...")
property_missing_count = section_property_name_comparison(file1,file2)
test_summary(property_missing_count == 0,
    "#{property_missing_count} properties exist in file1 but not file2")
    
# Test: check for mismatching property values
test_heading("Checking individual properties for matching values...")
property_value_mismatch_count = hierarchical_property_comparison(file1,file2)
test_summary(property_value_mismatch_count == 0,
    "#{property_value_mismatch_count} non-matching property values")

